using System.Collections.Generic;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.transaction
{


	using TransactionManager = javax.transaction.TransactionManager;
	using XAException = javax.transaction.xa.XAException;
	using XAResource = javax.transaction.xa.XAResource;
	using Xid = javax.transaction.xa.Xid;

	using Test = junit.framework.Test;
	using TestSuite = junit.framework.TestSuite;

	using Node = org.neo4j.graphdb.Node;
	using AbstractNeo4jTestCase = org.neo4j.kernel.impl.AbstractNeo4jTestCase;
	using TxModule = org.neo4j.kernel.impl.transaction.TxModule;
	using XaDataSourceManager = org.neo4j.kernel.impl.transaction.XaDataSourceManager;
	using XidImpl = org.neo4j.kernel.impl.transaction.XidImpl;
	using LogBuffer = org.neo4j.kernel.impl.transaction.xaframework.LogBuffer;
	using XaCommand = org.neo4j.kernel.impl.transaction.xaframework.XaCommand;
	using XaCommandFactory = org.neo4j.kernel.impl.transaction.xaframework.XaCommandFactory;
	using XaConnection = org.neo4j.kernel.impl.transaction.xaframework.XaConnection;
	using XaConnectionHelpImpl = org.neo4j.kernel.impl.transaction.xaframework.XaConnectionHelpImpl;
	using XaContainer = org.neo4j.kernel.impl.transaction.xaframework.XaContainer;
	using XaDataSource = org.neo4j.kernel.impl.transaction.xaframework.XaDataSource;
	using XaLogicalLog = org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog;
	using XaResourceHelpImpl = org.neo4j.kernel.impl.transaction.xaframework.XaResourceHelpImpl;
	using XaResourceManager = org.neo4j.kernel.impl.transaction.xaframework.XaResourceManager;
	using XaTransaction = org.neo4j.kernel.impl.transaction.xaframework.XaTransaction;
	using XaTransactionFactory = org.neo4j.kernel.impl.transaction.xaframework.XaTransactionFactory;

	public class TestXaFramework : AbstractNeo4jTestCase
	{
		private TransactionManager tm;
		private XaDataSourceManager xaDsMgr;

		public TestXaFramework(string name) : base(name)
		{
		}

		public static Test suite()
		{
			return new TestSuite(typeof(TestXaFramework));
		}

		public override void setUp()
		{
			base.setUp();
			getTransaction().finish();
			TxModule txModule = getEmbeddedGraphDb().getConfig().getTxModule();
			tm = txModule.getTxManager();
			xaDsMgr = txModule.getXaDataSourceManager();
		}

		private class DummyCommand : XaCommand
		{
			private int type = -1;

			internal DummyCommand(int type)
			{
				this.type = type;
			}

			public override void execute()
			{
			}

		// public void writeToFile( FileChannel fileChannel, ByteBuffer buffer )
		// throws IOException
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void writeToFile(LogBuffer buffer) throws IOException
			public override void writeToFile(LogBuffer buffer)
			{
			// buffer.clear();
				buffer.putInt(type);
			// buffer.flip();
			// fileChannel.write( buffer );
			}
		}

		private class DummyCommandFactory : XaCommandFactory
		{
//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public XaCommand readCommand(ReadableByteChannel byteChannel, ByteBuffer buffer) throws IOException
			public override XaCommand readCommand(ReadableByteChannel byteChannel, ByteBuffer buffer)
			{
				buffer.Clear();
				buffer.limit(4);
				if (byteChannel.read(buffer) == 4)
				{
					buffer.flip();
					return new DummyCommand(buffer.getInt());
				}
				return null;
			}
		}

		private class DummyTransaction : XaTransaction
		{
			private java.util.List<XaCommand> commandList = new List<XaCommand>();

			public DummyTransaction(int identifier, XaLogicalLog log) : base(identifier, log)
			{
			}

			public override void doAddCommand(XaCommand command)
			{
				commandList.Add(command);
			}

			public virtual XaCommand[] getCommands()
			{
				return commandList.ToArray(new XaCommand[commandList.size()]);
			}

			public override void doPrepare()
			{

			}

			public override void doRollback()
			{
			}

			public override void doCommit()
			{
			}

			public override bool isReadOnly()
			{
				return false;
			}
		}

		private class DummyTransactionFactory : XaTransactionFactory
		{
			public override XaTransaction create(int identifier)
			{
				return new DummyTransaction(identifier, getLogicalLog());
			}

			public override void flushAll()
			{

			}

			public override long getAndSetNewVersion()
			{
				return -1;
			}

			public override long getCurrentVersion()
			{
				return -1;
			}
		}

		public class DummyXaDataSource : XaDataSource
		{
			private XaContainer xaContainer = null;

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public DummyXaDataSource(java.util.Map<?,?> map) throws InstantiationException
			public DummyXaDataSource<T1>(java.util.Map<T1> map) : base(map)
			{
				try
				{
					xaContainer = XaContainer.create("dummy_resource", new DummyCommandFactory(), new DummyTransactionFactory(), null);
					xaContainer.openLogicalLog();
				}
				catch (IOException e)
				{
					throw new InstantiationException("" + e);
				}
			}

			public override void close()
			{
				xaContainer.Close();
			// cleanup dummy resource log
				File dir = new File(".");
				File[] files = dir.listFiles(new FilenameFilter() { public bool accept(File dir, string fileName) { return fileName.StartsWith("dummy_resource"); } });
				for (int i = 0; i < files.Length; i++)
				{
					files[i].delete();
				}
			}

			public override XaConnection getXaConnection()
			{
				return new DummyXaConnection(xaContainer.getResourceManager());
			}

			public override sbyte[] getBranchId()
			{
			// TODO Auto-generated method stub
				return null;
			}

			public override void setBranchId(sbyte[] branchId)
			{
			// TODO Auto-generated method stub

			}
		}

		private class DummyXaResource : XaResourceHelpImpl
		{
			internal DummyXaResource(XaResourceManager xaRm) : base(xaRm, null)
			{
			}

			public override bool isSameRM(XAResource resource)
			{
				if (resource is DummyXaResource)
				{
					return true;
				}
				return false;
			}
		}

		private class DummyXaConnection : XaConnectionHelpImpl
		{
			private XAResource xaResource = null;

			public DummyXaConnection(XaResourceManager xaRm) : base(xaRm)
			{
				xaResource = new DummyXaResource(xaRm);
			}

			public override XAResource getXaResource()
			{
				return xaResource;
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void doStuff1() throws XAException
			public virtual void doStuff1()
			{
				validate();
				getTransaction().addCommand(new DummyCommand(1));
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void doStuff2() throws XAException
			public virtual void doStuff2()
			{
				validate();
				getTransaction().addCommand(new DummyCommand(2));
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void enlistWithTx() throws Exception
			public virtual void enlistWithTx()
			{
				tm.getTransaction().enlistResource(xaResource);
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void delistFromTx() throws Exception
			public virtual void delistFromTx()
			{
				tm.getTransaction().delistResource(xaResource, XAResource.TMSUCCESS);
			}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public int getTransactionId() throws Exception
			public virtual int getTransactionId()
			{
				return getTransaction().getIdentifier();
			}
		}

		public virtual void testCreateXaResource()
		{
			try
			{
				xaDsMgr.registerDataSource("dummy_datasource", new DummyXaDataSource(new Dictionary<object, object>()), "DDDDDD".getBytes());
			}
			catch (Exception e)
			{
				fail("" + e);
			}
			XaDataSource xaDs = xaDsMgr.getXaDataSource("dummy_datasource");
			DummyXaConnection xaC = null;
			try
			{
				xaC = (DummyXaConnection) xaDs.getXaConnection();
				try
				{
					xaC.doStuff1();
					fail("Non enlisted resource should throw exception");
				}
				catch (XAException e)
				{ // good
				}
				Xid xid = new XidImpl(new sbyte[0], new sbyte[0]);
				xaC.getXaResource().Start(xid, XAResource.TMNOFLAGS);
				try
				{
					xaC.doStuff1();
					xaC.doStuff2();
				}
				catch (XAException e)
				{
					fail("Enlisted resource should not throw exception");
				}
				xaC.getXaResource().end(xid, XAResource.TMSUCCESS);
				xaC.getXaResource().prepare(xid);
				xaC.getXaResource().commit(xid, false);
			}
			catch (Exception e)
			{
				e.printStackTrace();
				fail("" + e);
			}
			finally
			{
				xaDsMgr.unregisterDataSource("dummy_datasource");
				if (xaC != null)
				{
					xaC.destroy();
				}
			}
		// cleanup dummy resource log
			File dir = new File(".");
			File[] files = dir.listFiles(new FilenameFilter() { public bool accept(File dir, string fileName) { return fileName.StartsWith("dummy_resource"); } });
			for (int i = 0; i < files.Length; i++)
			{
				files[i].delete();
			}
		}

		public virtual void testTxIdGeneration()
		{
			DummyXaDataSource xaDs1 = null;
			DummyXaConnection xaC1 = null;
			try
			{
				xaDsMgr.registerDataSource("dummy_datasource1", new DummyXaDataSource(new Dictionary<object, object>()), "DDDDDD".getBytes());
				xaDs1 = (DummyXaDataSource) xaDsMgr.getXaDataSource("dummy_datasource1");
				xaC1 = (DummyXaConnection) xaDs1.getXaConnection();
				tm.begin(); // get
				xaC1.enlistWithTx();
				int currentTxId = xaC1.getTransactionId();
				xaC1.doStuff1();
				xaC1.delistFromTx();
				tm.commit();
			// xaC2 = ( DummyXaConnection ) xaDs2.getXaConnection();
				tm.begin();
				Node node = getGraphDb().createNode(); // get resource in tx
				xaC1.enlistWithTx();
				assertEquals(++currentTxId, xaC1.getTransactionId());
				xaC1.doStuff1();
				xaC1.delistFromTx();
				tm.commit();
				tm.begin();
				node = getGraphDb().getNodeById((int) node.getId());
				xaC1.enlistWithTx();
				assertEquals(++currentTxId, xaC1.getTransactionId());
				xaC1.doStuff2();
				xaC1.delistFromTx();
				node.delete();
				tm.commit();
			}
			catch (Exception e)
			{
				e.printStackTrace();
				fail("" + e);
			}
			finally
			{
				xaDsMgr.unregisterDataSource("dummy_datasource1");
			// xaDsMgr.unregisterDataSource( "dummy_datasource1" );
				if (xaC1 != null)
				{
					xaC1.destroy();
				}
			}
		// cleanup dummy resource log
			File dir = new File(".");
			File[] files = dir.listFiles(new FilenameFilter() { public bool accept(File dir, string fileName) { return fileName.StartsWith("dummy_resource"); } });
			for (int i = 0; i < files.Length; i++)
			{
				files[i].delete();
			}
		}
	}
}